/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.alee.laf.menu;

import com.alee.utils.SwingUtils;

import javax.swing.*;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;

/**
 * Calculates preferred size and layouts menu items.
 */

public class MenuItemLayoutHelper
{
    /* Client Property keys for calculation of maximal widths */
    public static final StringUIClientPropertyKey MAX_ARROW_WIDTH = new StringUIClientPropertyKey ( "maxArrowWidth" );
    public static final StringUIClientPropertyKey MAX_CHECK_WIDTH = new StringUIClientPropertyKey ( "maxCheckWidth" );
    public static final StringUIClientPropertyKey MAX_ICON_WIDTH = new StringUIClientPropertyKey ( "maxIconWidth" );
    public static final StringUIClientPropertyKey MAX_TEXT_WIDTH = new StringUIClientPropertyKey ( "maxTextWidth" );
    public static final StringUIClientPropertyKey MAX_ACC_WIDTH = new StringUIClientPropertyKey ( "maxAccWidth" );
    public static final StringUIClientPropertyKey MAX_LABEL_WIDTH = new StringUIClientPropertyKey ( "maxLabelWidth" );
    public static final StringUIClientPropertyKey BASICMENUITEMUI_MAX_TEXT_OFFSET = new StringUIClientPropertyKey ( "maxTextOffset" );

    private JMenuItem mi;
    private JComponent miParent;

    private Font font;
    private Font accFont;
    private FontMetrics fm;
    private FontMetrics accFm;

    private Icon icon;
    private Icon checkIcon;
    private Icon arrowIcon;
    private String text;
    private String accText;

    private boolean isColumnLayout;
    private boolean useCheckAndArrow;
    private boolean isLeftToRight;
    private boolean isTopLevelMenu;
    private View htmlView;

    private int verticalAlignment;
    private int horizontalAlignment;
    private int verticalTextPosition;
    private int horizontalTextPosition;
    private int gap;
    private int leadingGap;
    private int afterCheckIconGap;
    private int minTextOffset;

    private Rectangle viewRect;

    private RectSize iconSize;
    private RectSize textSize;
    private RectSize accSize;
    private RectSize checkSize;
    private RectSize arrowSize;
    private RectSize labelSize;

    /**
     * The empty protected constructor is necessary for derived classes.
     */
    protected MenuItemLayoutHelper ()
    {
    }

    public MenuItemLayoutHelper ( JMenuItem mi, Icon checkIcon, Icon arrowIcon, Rectangle viewRect, int gap, String accDelimiter,
                                  boolean isLeftToRight, Font font, Font accFont, boolean useCheckAndArrow, String propertyPrefix )
    {
        reset ( mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter, isLeftToRight, font, accFont, useCheckAndArrow, propertyPrefix );
    }

    protected void reset ( JMenuItem mi, Icon checkIcon, Icon arrowIcon, Rectangle viewRect, int gap, String accDelimiter,
                           boolean isLeftToRight, Font font, Font accFont, boolean useCheckAndArrow, String propertyPrefix )
    {
        this.mi = mi;
        this.miParent = getMenuItemParent ( mi );
        this.accText = getAccText ( accDelimiter );
        this.verticalAlignment = mi.getVerticalAlignment ();
        this.horizontalAlignment = mi.getHorizontalAlignment ();
        this.verticalTextPosition = mi.getVerticalTextPosition ();
        this.horizontalTextPosition = mi.getHorizontalTextPosition ();
        this.useCheckAndArrow = useCheckAndArrow;
        this.font = font;
        this.accFont = accFont;
        this.fm = mi.getFontMetrics ( font );
        this.accFm = mi.getFontMetrics ( accFont );
        this.isLeftToRight = isLeftToRight;
        this.isColumnLayout = isColumnLayout ( isLeftToRight, horizontalAlignment, horizontalTextPosition, verticalTextPosition );
        this.isTopLevelMenu = this.miParent == null;
        this.checkIcon = checkIcon;
        this.icon = getIcon ( propertyPrefix );
        this.arrowIcon = arrowIcon;
        this.text = mi.getText ();
        this.gap = gap;
        this.afterCheckIconGap = getAfterCheckIconGap ( propertyPrefix );
        this.minTextOffset = getMinTextOffset ( propertyPrefix );
        this.htmlView = ( View ) mi.getClientProperty ( BasicHTML.propertyKey );
        this.viewRect = viewRect;

        this.iconSize = new RectSize ();
        this.textSize = new RectSize ();
        this.accSize = new RectSize ();
        this.checkSize = new RectSize ();
        this.arrowSize = new RectSize ();
        this.labelSize = new RectSize ();
        calcWidthsAndHeights ();
        setOriginalWidths ();
        calcMaxWidths ();

        this.leadingGap = getLeadingGap ( propertyPrefix );
        calcMaxTextOffset ( viewRect );
    }

    private void setOriginalWidths ()
    {
        iconSize.origWidth = iconSize.width;
        textSize.origWidth = textSize.width;
        accSize.origWidth = accSize.width;
        checkSize.origWidth = checkSize.width;
        arrowSize.origWidth = arrowSize.width;
    }

    private String getAccText ( String acceleratorDelimiter )
    {
        String accText = "";
        KeyStroke accelerator = mi.getAccelerator ();
        if ( accelerator != null )
        {
            int modifiers = accelerator.getModifiers ();
            if ( modifiers > 0 )
            {
                accText = KeyEvent.getKeyModifiersText ( modifiers );
                accText += acceleratorDelimiter;
            }
            int keyCode = accelerator.getKeyCode ();
            if ( keyCode != 0 )
            {
                accText += KeyEvent.getKeyText ( keyCode );
            }
            else
            {
                accText += accelerator.getKeyChar ();
            }
        }
        return accText;
    }

    private Icon getIcon ( String propertyPrefix )
    {
        return mi.getIcon ();
    }

    private int getMinTextOffset ( String propertyPrefix )
    {
        int minimumTextOffset = 0;
        Object minimumTextOffsetObject = UIManager.get ( propertyPrefix + ".minimumTextOffset" );
        if ( minimumTextOffsetObject instanceof Integer )
        {
            minimumTextOffset = ( Integer ) minimumTextOffsetObject;
        }
        return minimumTextOffset;
    }

    private int getAfterCheckIconGap ( String propertyPrefix )
    {
        int afterCheckIconGap = gap;
        Object afterCheckIconGapObject = UIManager.get ( propertyPrefix + ".afterCheckIconGap" );
        if ( afterCheckIconGapObject instanceof Integer )
        {
            afterCheckIconGap = ( Integer ) afterCheckIconGapObject;
        }
        return afterCheckIconGap;
    }

    private int getLeadingGap ( String propertyPrefix )
    {
        if ( checkSize.getMaxWidth () > 0 )
        {
            return getCheckOffset ( propertyPrefix );
        }
        else
        {
            return gap; // There is no any check icon
        }
    }

    private int getCheckOffset ( String propertyPrefix )
    {
        int checkIconOffset = gap;
        Object checkIconOffsetObject = UIManager.get ( propertyPrefix + ".checkIconOffset" );
        if ( checkIconOffsetObject instanceof Integer )
        {
            checkIconOffset = ( Integer ) checkIconOffsetObject;
        }
        return checkIconOffset;
    }

    protected void calcWidthsAndHeights ()
    {
        // iconRect
        if ( icon != null )
        {
            iconSize.width = icon.getIconWidth ();
            iconSize.height = icon.getIconHeight ();
        }
        else if ( useCheckAndArrow && arrowIcon != null )
        {
            // Fix for JMenu w/o icon width
            iconSize.width = 16;
            iconSize.height = 16;
        }

        // accRect
        if ( !accText.equals ( "" ) )
        {
            accSize.width = SwingUtils.stringWidth ( mi, accFm, accText );
            accSize.height = accFm.getHeight ();
        }

        // textRect
        if ( text == null )
        {
            text = "";
        }
        else if ( !text.equals ( "" ) )
        {
            if ( htmlView != null )
            {
                // Text is HTML
                textSize.width = ( int ) htmlView.getPreferredSpan ( View.X_AXIS );
                textSize.height = ( int ) htmlView.getPreferredSpan ( View.Y_AXIS );
            }
            else
            {
                // Text isn't HTML
                textSize.width = SwingUtils.stringWidth ( mi, fm, text );
                textSize.height = fm.getHeight ();
            }
        }

        if ( useCheckAndArrow )
        {
            // checkIcon
            if ( checkIcon != null )
            {
                checkSize.width = checkIcon.getIconWidth ();
                checkSize.height = checkIcon.getIconHeight ();
            }
            // arrowRect
            if ( arrowIcon != null )
            {
                arrowSize.width = arrowIcon.getIconWidth ();
                arrowSize.height = arrowIcon.getIconHeight ();
            }
        }

        // labelRect
        if ( isColumnLayout )
        {
            labelSize.width = iconSize.width + gap + textSize.width;
            labelSize.height = max ( checkSize.height, iconSize.height, textSize.height, accSize.height, arrowSize.height );
        }
        else
        {
            Rectangle textRect = new Rectangle ();
            Rectangle iconRect = new Rectangle ();
            SwingUtilities.layoutCompoundLabel ( mi, fm, text, icon, verticalAlignment, horizontalAlignment, verticalTextPosition,
                    horizontalTextPosition, viewRect, iconRect, textRect, gap );

            Rectangle labelRect = iconRect.union ( textRect );
            labelSize.height = labelRect.height;
            labelSize.width = labelRect.width;
        }
    }

    protected void calcMaxWidths ()
    {
        calcMaxWidth ( checkSize, MAX_CHECK_WIDTH );
        calcMaxWidth ( arrowSize, MAX_ARROW_WIDTH );
        calcMaxWidth ( accSize, MAX_ACC_WIDTH );

        if ( isColumnLayout )
        {
            calcMaxWidth ( iconSize, MAX_ICON_WIDTH );
            calcMaxWidth ( textSize, MAX_TEXT_WIDTH );
            int curGap = gap;
            if ( ( iconSize.getMaxWidth () == 0 ) || ( textSize.getMaxWidth () == 0 ) )
            {
                curGap = 0;
            }
            labelSize.maxWidth = calcMaxValue ( MAX_LABEL_WIDTH, iconSize.maxWidth + textSize.maxWidth + curGap );
        }
        else
        {
            // We shouldn't use current icon and text widths
            // in maximal widths calculation for complex layout.
            iconSize.maxWidth = getParentIntProperty ( MAX_ICON_WIDTH );
            calcMaxWidth ( labelSize, MAX_LABEL_WIDTH );
            // If maxLabelWidth is wider
            // than the widest icon + the widest text + gap,
            // we should update the maximal text witdh
            int candidateTextWidth = labelSize.maxWidth - iconSize.maxWidth;
            if ( iconSize.maxWidth > 0 )
            {
                candidateTextWidth -= gap;
            }
            textSize.maxWidth = calcMaxValue ( MAX_TEXT_WIDTH, candidateTextWidth );
        }
    }

    protected void calcMaxWidth ( RectSize rs, Object key )
    {
        rs.maxWidth = calcMaxValue ( key, rs.width );
    }

    /**
     * Calculates and returns maximal value through specified parent component client property.
     *
     * @param propertyName name of the property, which stores the maximal value.
     * @param value        a value which pretends to be maximal
     * @return maximal value among the parent property and the value.
     */
    protected int calcMaxValue ( Object propertyName, int value )
    {
        // Get maximal value from parent client property
        int maxValue = getParentIntProperty ( propertyName );
        // Store new maximal width in parent client property
        if ( value > maxValue )
        {
            if ( miParent != null )
            {
                miParent.putClientProperty ( propertyName, value );
            }
            return value;
        }
        else
        {
            return maxValue;
        }
    }

    /**
     * Returns parent client property as int.
     *
     * @param propertyName name of the parent property.
     * @return value of the property as int.
     */
    protected int getParentIntProperty ( Object propertyName )
    {
        Object value = null;
        if ( miParent != null )
        {
            value = miParent.getClientProperty ( propertyName );
        }
        if ( ( value == null ) || !( value instanceof Integer ) )
        {
            value = 0;
        }
        return ( Integer ) value;
    }

    public static boolean isColumnLayout ( boolean isLeftToRight, JMenuItem mi )
    {
        assert ( mi != null );
        return isColumnLayout ( isLeftToRight, mi.getHorizontalAlignment (), mi.getHorizontalTextPosition (),
                mi.getVerticalTextPosition () );
    }

    /**
     * Answers should we do column layout for a menu item or not. We do it when a user doesn't set any alignmentst set any alignments and
     * text positions manually, except the vertical alignment.
     */
    public static boolean isColumnLayout ( boolean isLeftToRight, int horizontalAlignment, int horizontalTextPosition,
                                           int verticalTextPosition )
    {
        if ( verticalTextPosition != SwingConstants.CENTER )
        {
            return false;
        }
        if ( isLeftToRight )
        {
            if ( horizontalAlignment != SwingConstants.LEADING && horizontalAlignment != SwingConstants.LEFT )
            {
                return false;
            }
            if ( horizontalTextPosition != SwingConstants.TRAILING && horizontalTextPosition != SwingConstants.RIGHT )
            {
                return false;
            }
        }
        else
        {
            if ( horizontalAlignment != SwingConstants.LEADING && horizontalAlignment != SwingConstants.RIGHT )
            {
                return false;
            }
            if ( horizontalTextPosition != SwingConstants.TRAILING && horizontalTextPosition != SwingConstants.LEFT )
            {
                return false;
            }
        }
        return true;
    }

    /**
     * Calculates maximal text offset. It is required for some L&Fs (ex: Vista L&F). The offset is meaningful only for L2R column layout.
     *
     * @param viewRect the rectangle, the maximal text offset will be calculated for.
     */
    private void calcMaxTextOffset ( Rectangle viewRect )
    {
        if ( !isColumnLayout || !isLeftToRight )
        {
            return;
        }

        // Calculate the current text offset
        int offset = viewRect.x + leadingGap + checkSize.maxWidth + afterCheckIconGap +
                iconSize.maxWidth + gap;
        if ( checkSize.maxWidth == 0 )
        {
            offset -= afterCheckIconGap;
        }
        if ( iconSize.maxWidth == 0 )
        {
            offset -= gap;
        }

        // maximal text offset shouldn't be less than minimal text offset;
        if ( offset < minTextOffset )
        {
            offset = minTextOffset;
        }

        // Calculate and store the maximal text offset
        calcMaxValue ( BASICMENUITEMUI_MAX_TEXT_OFFSET, offset );
    }

    /**
     * Layout icon, text, check icon, accelerator text and arrow icon in the viewRect and return their positions.
     * <p/>
     * If horizontalAlignment, verticalTextPosition and horizontalTextPosition are default (user doesn't set any manually) the layouting
     * algorithm is:t set any manually) the layouting algorithm is: Elements are layouted in the five columns: check icon + icon + text +
     * accelerator text + arrow icon
     * <p/>
     * In the other case elements are layouted in the four columns: check icon + label + accelerator text + arrow icon Label is union of
     * icon and text.
     * <p/>
     * The order of columns can be reversed. It depends on the menu item orientation.
     */
    public LayoutResult layoutMenuItem ()
    {
        LayoutResult lr = createLayoutResult ();
        prepareForLayout ( lr );

        if ( isColumnLayout () )
        {
            if ( isLeftToRight () )
            {
                doLTRColumnLayout ( lr, getLTRColumnAlignment () );
            }
            else
            {
                doRTLColumnLayout ( lr, getRTLColumnAlignment () );
            }
        }
        else
        {
            if ( isLeftToRight () )
            {
                doLTRComplexLayout ( lr, getLTRColumnAlignment () );
            }
            else
            {
                doRTLComplexLayout ( lr, getRTLColumnAlignment () );
            }
        }

        alignAccCheckAndArrowVertically ( lr );
        return lr;
    }

    private LayoutResult createLayoutResult ()
    {
        return new LayoutResult ( new Rectangle ( iconSize.width, iconSize.height ), new Rectangle ( textSize.width, textSize.height ),
                new Rectangle ( accSize.width, accSize.height ), new Rectangle ( checkSize.width, checkSize.height ),
                new Rectangle ( arrowSize.width, arrowSize.height ), new Rectangle ( labelSize.width, labelSize.height ) );
    }

    public ColumnAlignment getLTRColumnAlignment ()
    {
        return ColumnAlignment.LEFT_ALIGNMENT;
    }

    public ColumnAlignment getRTLColumnAlignment ()
    {
        return ColumnAlignment.RIGHT_ALIGNMENT;
    }

    protected void prepareForLayout ( LayoutResult lr )
    {
        lr.checkRect.width = checkSize.maxWidth;
        lr.accRect.width = accSize.maxWidth;
        lr.arrowRect.width = arrowSize.maxWidth;
    }

    /**
     * Aligns the accelertor text and the check and arrow icons vertically with the center of the label rect.
     */
    private void alignAccCheckAndArrowVertically ( LayoutResult lr )
    {
        lr.accRect.y = ( int ) ( lr.labelRect.y + ( float ) lr.labelRect.height / 2 - ( float ) lr.accRect.height / 2 );
        fixVerticalAlignment ( lr, lr.accRect );
        if ( useCheckAndArrow )
        {
            lr.arrowRect.y = ( int ) ( lr.labelRect.y + ( float ) lr.labelRect.height / 2 - ( float ) lr.arrowRect.height / 2 );
            lr.checkRect.y = ( int ) ( lr.labelRect.y + ( float ) lr.labelRect.height / 2 - ( float ) lr.checkRect.height / 2 );
            fixVerticalAlignment ( lr, lr.arrowRect );
            fixVerticalAlignment ( lr, lr.checkRect );
        }
    }

    /**
     * Fixes vertical alignment of all menu item elements if rect.y or (rect.y + rect.height) is out of viewRect bounds
     */
    private void fixVerticalAlignment ( LayoutResult lr, Rectangle r )
    {
        int delta = 0;
        if ( r.y < viewRect.y )
        {
            delta = viewRect.y - r.y;
        }
        else if ( r.y + r.height > viewRect.y + viewRect.height )
        {
            delta = viewRect.y + viewRect.height - r.y - r.height;
        }
        if ( delta != 0 )
        {
            lr.checkRect.y += delta;
            lr.iconRect.y += delta;
            lr.textRect.y += delta;
            lr.accRect.y += delta;
            lr.arrowRect.y += delta;
            lr.labelRect.y += delta;
        }
    }

    private void doLTRColumnLayout ( LayoutResult lr, ColumnAlignment alignment )
    {
        // Set maximal width for all the five basic rects
        // (three other ones are already maximal)
        lr.iconRect.width = iconSize.maxWidth;
        lr.textRect.width = textSize.maxWidth;

        // Set X coordinates
        // All rects will be aligned at the left side
        calcXPositionsLTR ( viewRect.x, leadingGap, gap, lr.checkRect, lr.iconRect, lr.textRect );

        // Tune afterCheckIconGap
        if ( lr.checkRect.width > 0 )
        { // there is the afterCheckIconGap
            lr.iconRect.x += afterCheckIconGap - gap;
            lr.textRect.x += afterCheckIconGap - gap;
        }

        calcXPositionsRTL ( viewRect.x + viewRect.width, leadingGap, gap, lr.arrowRect, lr.accRect );

        // Take into account minimal text offset
        int textOffset = lr.textRect.x - viewRect.x;
        if ( !isTopLevelMenu && ( textOffset < minTextOffset ) )
        {
            lr.textRect.x += minTextOffset - textOffset;
        }

        alignRects ( lr, alignment );

        // Take into account the left side bearings for text and accelerator text.
        fixTextRects ( lr );

        // Set Y coordinate for text and icon.
        // Y coordinates for other rects
        // will be calculated later in layoutMenuItem.
        calcTextAndIconYPositions ( lr );

        // Calculate valid X and Y coordinates for labelRect
        lr.setLabelRect ( lr.textRect.union ( lr.iconRect ) );
    }

    private void doLTRComplexLayout ( LayoutResult lr, ColumnAlignment alignment )
    {
        lr.labelRect.width = labelSize.maxWidth;

        // Set X coordinates
        calcXPositionsLTR ( viewRect.x, leadingGap, gap, lr.checkRect, lr.labelRect );

        // Tune afterCheckIconGap
        if ( lr.checkRect.width > 0 )
        { // there is the afterCheckIconGap
            lr.labelRect.x += afterCheckIconGap - gap;
        }

        calcXPositionsRTL ( viewRect.x + viewRect.width, leadingGap, gap, lr.arrowRect, lr.accRect );

        // Take into account minimal text offset
        int labelOffset = lr.labelRect.x - viewRect.x;
        if ( !isTopLevelMenu && ( labelOffset < minTextOffset ) )
        {
            lr.labelRect.x += minTextOffset - labelOffset;
        }

        alignRects ( lr, alignment );

        // Take into account the left side bearing for accelerator text.
        // The LSB for text is taken into account in layoutCompoundLabel() below.
        fixAccTextRect ( lr );

        // Center labelRect vertically
        calcLabelYPosition ( lr );

        layoutIconAndTextInLabelRect ( lr );
    }

    private void doRTLColumnLayout ( LayoutResult lr, ColumnAlignment alignment )
    {
        // Set maximal width for all the five basic rects
        // (three other ones are already maximal)
        lr.iconRect.width = iconSize.maxWidth;
        lr.textRect.width = textSize.maxWidth;

        // Set X coordinates
        calcXPositionsRTL ( viewRect.x + viewRect.width, leadingGap, gap, lr.checkRect, lr.iconRect, lr.textRect );

        // Tune the gap after check icon
        if ( lr.checkRect.width > 0 )
        { // there is the gap after check icon
            lr.iconRect.x -= afterCheckIconGap - gap;
            lr.textRect.x -= afterCheckIconGap - gap;
        }

        calcXPositionsLTR ( viewRect.x, leadingGap, gap, lr.arrowRect, lr.accRect );

        // Take into account minimal text offset
        int textOffset = ( viewRect.x + viewRect.width ) - ( lr.textRect.x + lr.textRect.width );
        if ( !isTopLevelMenu && ( textOffset < minTextOffset ) )
        {
            lr.textRect.x -= minTextOffset - textOffset;
        }

        alignRects ( lr, alignment );

        // Take into account the left side bearings for text and accelerator text.
        fixTextRects ( lr );

        // Set Y coordinates for text and icon.
        // Y coordinates for other rects
        // will be calculated later in layoutMenuItem.
        calcTextAndIconYPositions ( lr );

        // Calculate valid X and Y coordinate for labelRect
        lr.setLabelRect ( lr.textRect.union ( lr.iconRect ) );
    }

    private void doRTLComplexLayout ( LayoutResult lr, ColumnAlignment alignment )
    {
        lr.labelRect.width = labelSize.maxWidth;

        // Set X coordinates
        calcXPositionsRTL ( viewRect.x + viewRect.width, leadingGap, gap, lr.checkRect, lr.labelRect );

        // Tune the gap after check icon
        if ( lr.checkRect.width > 0 )
        { // there is the gap after check icon
            lr.labelRect.x -= afterCheckIconGap - gap;
        }

        calcXPositionsLTR ( viewRect.x, leadingGap, gap, lr.arrowRect, lr.accRect );

        // Take into account minimal text offset
        int labelOffset = ( viewRect.x + viewRect.width ) - ( lr.labelRect.x + lr.labelRect.width );
        if ( !isTopLevelMenu && ( labelOffset < minTextOffset ) )
        {
            lr.labelRect.x -= minTextOffset - labelOffset;
        }

        alignRects ( lr, alignment );

        // Take into account the left side bearing for accelerator text.
        // The LSB for text is taken into account in layoutCompoundLabel() below.
        fixAccTextRect ( lr );

        // Center labelRect vertically
        calcLabelYPosition ( lr );

        layoutIconAndTextInLabelRect ( lr );
    }

    private void alignRects ( LayoutResult lr, ColumnAlignment alignment )
    {
        alignRect ( lr.checkRect, alignment.getCheckAlignment (), checkSize.getOrigWidth () );
        alignRect ( lr.iconRect, alignment.getIconAlignment (), iconSize.getOrigWidth () );
        alignRect ( lr.textRect, alignment.getTextAlignment (), textSize.getOrigWidth () );
        alignRect ( lr.accRect, alignment.getAccAlignment (), accSize.getOrigWidth () );
        alignRect ( lr.arrowRect, alignment.getArrowAlignment (), arrowSize.getOrigWidth () );
    }

    private void alignRect ( Rectangle rect, int alignment, int origWidth )
    {
        if ( alignment != SwingUtilities.LEFT )
        {
            rect.x = rect.x + rect.width - origWidth;
            rect.width = origWidth;
        }
    }

    protected void layoutIconAndTextInLabelRect ( LayoutResult lr )
    {
        lr.setTextRect ( new Rectangle () );
        lr.setIconRect ( new Rectangle () );
        SwingUtilities.layoutCompoundLabel ( mi, fm, text, icon, verticalAlignment, horizontalAlignment, verticalTextPosition,
                horizontalTextPosition, lr.labelRect, lr.iconRect, lr.textRect, gap );
    }

    private void calcXPositionsLTR ( int startXPos, int leadingGap, int gap, Rectangle... rects )
    {
        int curXPos = startXPos + leadingGap;
        for ( Rectangle rect : rects )
        {
            rect.x = curXPos;
            if ( rect.width > 0 )
            {
                curXPos += rect.width + gap;
            }
        }
    }

    private void calcXPositionsRTL ( int startXPos, int leadingGap, int gap, Rectangle... rects )
    {
        int curXPos = startXPos - leadingGap;
        for ( Rectangle rect : rects )
        {
            rect.x = curXPos - rect.width;
            if ( rect.width > 0 )
            {
                curXPos -= rect.width + gap;
            }
        }
    }

    /**
     * Takes into account the left side bearings for text and accelerator text
     */
    private void fixTextRects ( LayoutResult lr )
    {
        if ( htmlView == null )
        {
            // The text isn't a HTML
            int lsb = SwingUtils.getLeftSideBearing ( mi, fm, text );
            if ( lsb < 0 )
            {
                lr.textRect.x -= lsb;
            }
        }
        fixAccTextRect ( lr );
    }

    /**
     * Takes into account the left side bearing for accelerator text
     */
    private void fixAccTextRect ( LayoutResult lr )
    {
        int lsb = SwingUtils.getLeftSideBearing ( mi, accFm, accText );
        if ( lsb < 0 )
        {
            lr.accRect.x -= lsb;
        }
    }

    /**
     * Sets Y coordinates of text and icon taking into account the vertical alignment
     */
    private void calcTextAndIconYPositions ( LayoutResult lr )
    {
        if ( verticalAlignment == SwingUtilities.TOP )
        {
            lr.textRect.y = ( int ) ( viewRect.y + ( float ) lr.labelRect.height / 2 - ( float ) lr.textRect.height / 2 );
            lr.iconRect.y = ( int ) ( viewRect.y + ( float ) lr.labelRect.height / 2 - ( float ) lr.iconRect.height / 2 );
        }
        else if ( verticalAlignment == SwingUtilities.CENTER )
        {
            lr.textRect.y = ( int ) ( viewRect.y + ( float ) viewRect.height / 2 - ( float ) lr.textRect.height / 2 );
            lr.iconRect.y = ( int ) ( viewRect.y + ( float ) viewRect.height / 2 - ( float ) lr.iconRect.height / 2 );
        }
        else if ( verticalAlignment == SwingUtilities.BOTTOM )
        {
            lr.textRect.y = ( int ) ( viewRect.y + viewRect.height - ( float ) lr.labelRect.height / 2 -
                    ( float ) lr.textRect.height / 2 );
            lr.iconRect.y = ( int ) ( viewRect.y + viewRect.height - ( float ) lr.labelRect.height / 2 -
                    ( float ) lr.iconRect.height / 2 );
        }
    }

    /**
     * Sets labelRect Y coordinate taking into account the vertical alignment
     */
    private void calcLabelYPosition ( LayoutResult lr )
    {
        if ( verticalAlignment == SwingUtilities.TOP )
        {
            lr.labelRect.y = viewRect.y;
        }
        else if ( verticalAlignment == SwingUtilities.CENTER )
        {
            lr.labelRect.y = ( int ) ( viewRect.y + ( float ) viewRect.height / 2 - ( float ) lr.labelRect.height / 2 );
        }
        else if ( verticalAlignment == SwingUtilities.BOTTOM )
        {
            lr.labelRect.y = viewRect.y + viewRect.height - lr.labelRect.height;
        }
    }

    /**
     * Returns parent of this component if it is not a top-level menu Otherwise returns null.
     *
     * @param menuItem the menu item whose parent will be returned.
     * @return parent of this component if it is not a top-level menu Otherwise returns null.
     */
    public static JComponent getMenuItemParent ( JMenuItem menuItem )
    {
        Container parent = menuItem.getParent ();
        if ( ( parent instanceof JComponent ) && ( !( menuItem instanceof JMenu ) || !( ( JMenu ) menuItem ).isTopLevelMenu () ) )
        {
            return ( JComponent ) parent;
        }
        else
        {
            return null;
        }
    }

    public static void clearUsedParentClientProperties ( JMenuItem menuItem )
    {
        clearUsedClientProperties ( getMenuItemParent ( menuItem ) );
    }

    public static void clearUsedClientProperties ( JComponent c )
    {
        if ( c != null )
        {
            c.putClientProperty ( MAX_ARROW_WIDTH, null );
            c.putClientProperty ( MAX_CHECK_WIDTH, null );
            c.putClientProperty ( MAX_ACC_WIDTH, null );
            c.putClientProperty ( MAX_TEXT_WIDTH, null );
            c.putClientProperty ( MAX_ICON_WIDTH, null );
            c.putClientProperty ( MAX_LABEL_WIDTH, null );
            c.putClientProperty ( BASICMENUITEMUI_MAX_TEXT_OFFSET, null );
        }
    }

    /**
     * Finds and returns maximal integer value in the given array.
     *
     * @param values array where the search will be performed.
     * @return maximal vaule.
     */
    public static int max ( int... values )
    {
        int maxValue = Integer.MIN_VALUE;
        for ( int i : values )
        {
            if ( i > maxValue )
            {
                maxValue = i;
            }
        }
        return maxValue;
    }

    public static Rectangle createMaxRect ()
    {
        return new Rectangle ( 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE );
    }

    public static void addMaxWidth ( RectSize size, int gap, Dimension result )
    {
        if ( size.maxWidth > 0 )
        {
            result.width += size.maxWidth + gap;
        }
    }

    public static void addWidth ( int width, int gap, Dimension result )
    {
        if ( width > 0 )
        {
            result.width += width + gap;
        }
    }

    public JMenuItem getMenuItem ()
    {
        return mi;
    }

    public JComponent getMenuItemParent ()
    {
        return miParent;
    }

    public Font getFont ()
    {
        return font;
    }

    public Font getAccFont ()
    {
        return accFont;
    }

    public FontMetrics getFontMetrics ()
    {
        return fm;
    }

    public FontMetrics getAccFontMetrics ()
    {
        return accFm;
    }

    public Icon getIcon ()
    {
        return icon;
    }

    public Icon getCheckIcon ()
    {
        return checkIcon;
    }

    public Icon getArrowIcon ()
    {
        return arrowIcon;
    }

    public String getText ()
    {
        return text;
    }

    public String getAccText ()
    {
        return accText;
    }

    public boolean isColumnLayout ()
    {
        return isColumnLayout;
    }

    public boolean useCheckAndArrow ()
    {
        return useCheckAndArrow;
    }

    public boolean isLeftToRight ()
    {
        return isLeftToRight;
    }

    public boolean isTopLevelMenu ()
    {
        return isTopLevelMenu;
    }

    public View getHtmlView ()
    {
        return htmlView;
    }

    public int getVerticalAlignment ()
    {
        return verticalAlignment;
    }

    public int getHorizontalAlignment ()
    {
        return horizontalAlignment;
    }

    public int getVerticalTextPosition ()
    {
        return verticalTextPosition;
    }

    public int getHorizontalTextPosition ()
    {
        return horizontalTextPosition;
    }

    public int getGap ()
    {
        return gap;
    }

    public int getLeadingGap ()
    {
        return leadingGap;
    }

    public int getAfterCheckIconGap ()
    {
        return afterCheckIconGap;
    }

    public int getMinTextOffset ()
    {
        return minTextOffset;
    }

    public Rectangle getViewRect ()
    {
        return viewRect;
    }

    public RectSize getIconSize ()
    {
        return iconSize;
    }

    public RectSize getTextSize ()
    {
        return textSize;
    }

    public RectSize getAccSize ()
    {
        return accSize;
    }

    public RectSize getCheckSize ()
    {
        return checkSize;
    }

    public RectSize getArrowSize ()
    {
        return arrowSize;
    }

    public RectSize getLabelSize ()
    {
        return labelSize;
    }

    protected void setMenuItem ( JMenuItem mi )
    {
        this.mi = mi;
    }

    protected void setMenuItemParent ( JComponent miParent )
    {
        this.miParent = miParent;
    }

    protected void setFont ( Font font )
    {
        this.font = font;
    }

    protected void setAccFont ( Font accFont )
    {
        this.accFont = accFont;
    }

    protected void setFontMetrics ( FontMetrics fm )
    {
        this.fm = fm;
    }

    protected void setAccFontMetrics ( FontMetrics accFm )
    {
        this.accFm = accFm;
    }

    protected void setIcon ( Icon icon )
    {
        this.icon = icon;
    }

    protected void setCheckIcon ( Icon checkIcon )
    {
        this.checkIcon = checkIcon;
    }

    protected void setArrowIcon ( Icon arrowIcon )
    {
        this.arrowIcon = arrowIcon;
    }

    protected void setText ( String text )
    {
        this.text = text;
    }

    protected void setAccText ( String accText )
    {
        this.accText = accText;
    }

    protected void setColumnLayout ( boolean columnLayout )
    {
        isColumnLayout = columnLayout;
    }

    protected void setUseCheckAndArrow ( boolean useCheckAndArrow )
    {
        this.useCheckAndArrow = useCheckAndArrow;
    }

    protected void setLeftToRight ( boolean leftToRight )
    {
        isLeftToRight = leftToRight;
    }

    protected void setTopLevelMenu ( boolean topLevelMenu )
    {
        isTopLevelMenu = topLevelMenu;
    }

    protected void setHtmlView ( View htmlView )
    {
        this.htmlView = htmlView;
    }

    protected void setVerticalAlignment ( int verticalAlignment )
    {
        this.verticalAlignment = verticalAlignment;
    }

    protected void setHorizontalAlignment ( int horizontalAlignment )
    {
        this.horizontalAlignment = horizontalAlignment;
    }

    protected void setVerticalTextPosition ( int verticalTextPosition )
    {
        this.verticalTextPosition = verticalTextPosition;
    }

    protected void setHorizontalTextPosition ( int horizontalTextPosition )
    {
        this.horizontalTextPosition = horizontalTextPosition;
    }

    protected void setGap ( int gap )
    {
        this.gap = gap;
    }

    protected void setLeadingGap ( int leadingGap )
    {
        this.leadingGap = leadingGap;
    }

    protected void setAfterCheckIconGap ( int afterCheckIconGap )
    {
        this.afterCheckIconGap = afterCheckIconGap;
    }

    protected void setMinTextOffset ( int minTextOffset )
    {
        this.minTextOffset = minTextOffset;
    }

    protected void setViewRect ( Rectangle viewRect )
    {
        this.viewRect = viewRect;
    }

    protected void setIconSize ( RectSize iconSize )
    {
        this.iconSize = iconSize;
    }

    protected void setTextSize ( RectSize textSize )
    {
        this.textSize = textSize;
    }

    protected void setAccSize ( RectSize accSize )
    {
        this.accSize = accSize;
    }

    protected void setCheckSize ( RectSize checkSize )
    {
        this.checkSize = checkSize;
    }

    protected void setArrowSize ( RectSize arrowSize )
    {
        this.arrowSize = arrowSize;
    }

    protected void setLabelSize ( RectSize labelSize )
    {
        this.labelSize = labelSize;
    }

    /**
     * Returns false if the component is a JMenu and it is a top level menu (on the menubar).
     */
    public static boolean useCheckAndArrow ( JMenuItem menuItem )
    {
        boolean b = true;
        if ( ( menuItem instanceof JMenu ) && ( ( ( JMenu ) menuItem ).isTopLevelMenu () ) )
        {
            b = false;
        }
        return b;
    }

    public static class LayoutResult
    {
        private Rectangle iconRect;
        private Rectangle textRect;
        private Rectangle accRect;
        private Rectangle checkRect;
        private Rectangle arrowRect;
        private Rectangle labelRect;

        public LayoutResult ()
        {
            iconRect = new Rectangle ();
            textRect = new Rectangle ();
            accRect = new Rectangle ();
            checkRect = new Rectangle ();
            arrowRect = new Rectangle ();
            labelRect = new Rectangle ();
        }

        public LayoutResult ( Rectangle iconRect, Rectangle textRect, Rectangle accRect, Rectangle checkRect, Rectangle arrowRect,
                              Rectangle labelRect )
        {
            this.iconRect = iconRect;
            this.textRect = textRect;
            this.accRect = accRect;
            this.checkRect = checkRect;
            this.arrowRect = arrowRect;
            this.labelRect = labelRect;
        }

        public Rectangle getIconRect ()
        {
            return iconRect;
        }

        public void setIconRect ( Rectangle iconRect )
        {
            this.iconRect = iconRect;
        }

        public Rectangle getTextRect ()
        {
            return textRect;
        }

        public void setTextRect ( Rectangle textRect )
        {
            this.textRect = textRect;
        }

        public Rectangle getAccRect ()
        {
            return accRect;
        }

        public void setAccRect ( Rectangle accRect )
        {
            this.accRect = accRect;
        }

        public Rectangle getCheckRect ()
        {
            return checkRect;
        }

        public void setCheckRect ( Rectangle checkRect )
        {
            this.checkRect = checkRect;
        }

        public Rectangle getArrowRect ()
        {
            return arrowRect;
        }

        public void setArrowRect ( Rectangle arrowRect )
        {
            this.arrowRect = arrowRect;
        }

        public Rectangle getLabelRect ()
        {
            return labelRect;
        }

        public void setLabelRect ( Rectangle labelRect )
        {
            this.labelRect = labelRect;
        }

        public Map getAllRects ()
        {
            Map result = new HashMap ();
            result.put ( "checkRect", checkRect );
            result.put ( "iconRect", iconRect );
            result.put ( "textRect", textRect );
            result.put ( "accRect", accRect );
            result.put ( "arrowRect", arrowRect );
            result.put ( "labelRect", labelRect );
            return result;
        }
    }

    public static class ColumnAlignment
    {
        private int checkAlignment;
        private int iconAlignment;
        private int textAlignment;
        private int accAlignment;
        private int arrowAlignment;

        public static final ColumnAlignment LEFT_ALIGNMENT =
                new ColumnAlignment ( SwingConstants.LEFT, SwingConstants.LEFT, SwingConstants.LEFT, SwingConstants.LEFT,
                        SwingConstants.LEFT );

        public static final ColumnAlignment RIGHT_ALIGNMENT =
                new ColumnAlignment ( SwingConstants.RIGHT, SwingConstants.RIGHT, SwingConstants.RIGHT, SwingConstants.RIGHT,
                        SwingConstants.RIGHT );

        public ColumnAlignment ( int checkAlignment, int iconAlignment, int textAlignment, int accAlignment, int arrowAlignment )
        {
            this.checkAlignment = checkAlignment;
            this.iconAlignment = iconAlignment;
            this.textAlignment = textAlignment;
            this.accAlignment = accAlignment;
            this.arrowAlignment = arrowAlignment;
        }

        public int getCheckAlignment ()
        {
            return checkAlignment;
        }

        public int getIconAlignment ()
        {
            return iconAlignment;
        }

        public int getTextAlignment ()
        {
            return textAlignment;
        }

        public int getAccAlignment ()
        {
            return accAlignment;
        }

        public int getArrowAlignment ()
        {
            return arrowAlignment;
        }
    }

    public static class RectSize
    {
        private int width;
        private int height;
        private int origWidth;
        private int maxWidth;

        public RectSize ()
        {
        }

        public RectSize ( int width, int height, int origWidth, int maxWidth )
        {
            this.width = width;
            this.height = height;
            this.origWidth = origWidth;
            this.maxWidth = maxWidth;
        }

        public int getWidth ()
        {
            return width;
        }

        public int getHeight ()
        {
            return height;
        }

        public int getOrigWidth ()
        {
            return origWidth;
        }

        public int getMaxWidth ()
        {
            return maxWidth;
        }

        public void setWidth ( int width )
        {
            this.width = width;
        }

        public void setHeight ( int height )
        {
            this.height = height;
        }

        public void setOrigWidth ( int origWidth )
        {
            this.origWidth = origWidth;
        }

        public void setMaxWidth ( int maxWidth )
        {
            this.maxWidth = maxWidth;
        }

        public String toString ()
        {
            return "[w=" + width + ",h=" + height + ",ow=" + origWidth + ",mw=" + maxWidth + "]";
        }
    }
}
